<script setup lang="ts">
import { Edit, SuccessFilled, CloseBold, Switch, Plus, Minus } from '@element-plus/icons-vue'
import {ElSwitch, ElInput, ElInputNumber, ElMessageBox, ElMessage} from "element-plus"
import { isIcon, isNumber } from "@/helpers/uses"
import {debugLog} from '@/helpers'
import { set, get } from '@vueuse/core'

interface Props {
  obj: Record<string, any> // 绑定的数据对象
  objId?: string           // 对象的ID
  editable?: boolean       // 是否可编辑
  titleKey?: string|((obj: Props['obj']) => string)   // 用于title展示的key名称
  lockKey?: boolean        // 是否锁定对象的key，true：不允许增添key。默认true
  immutables?: string[]    // 不允许修改值的key列表，只在editable为true时有效
  keySize?: string         // key 展示的width
  keyMap?: Record<string, string>
  hiddens?: string[]|((key: string) => boolean) // 需要隐藏的key列表
  status?: 'normal'|'edit' // 默认状态：普通 | 编辑
}
const props = withDefaults(defineProps<Props>(), {
  keySize: "120px",
  editable: false,
  lockKey: false,
  titleKey: '',
  status: 'normal',
  immutables: () => ['id'],
  hiddens: () => [] as string[]
})

const emit = defineEmits<{
  (event: 'change', target:
    { objId: string, newValue: Props['obj'], oldValue: Props['obj'], 
   refreshRaw: (() => void), reset: (() => void) }
  ): void
}>()

const editing = ref(props.status === 'edit')
let raw = _.cloneDeep(props.obj)
let changed = false

const reset = () => {
  for(const key in props.obj) {
    if (!(key in raw)) {
      delete props.obj[key]
    }
  }
  for(const key in raw) {
    props.obj[key] = raw[key]
  }
  changed = false
}

watch(() => _.cloneDeep(props.obj), (newValue, oldValue) => {
  changed = !_.isEqual(newValue, raw)
  debugLog('(AutoProtoCard)data change:', changed, newValue, raw)
}, { deep: true })

const entries = computed(() => _.toPairs(get(props.obj)).filter(([key, _]) => {
  switch(typeof props.hiddens) {
    case 'function': return !props.hiddens(key)
    default: return !props.hiddens.includes(key)
  }
}))
const prettyKey = (key: string) => _.get(props.keyMap, key) || key

function switchEditable() {
  set(editing, !get(editing))
}

function switchType(key: keyof Props['obj']): void {
  const v = props.obj[key]
  switch(typeof v) {
    case 'number':
      props.obj[key] = v.toString()
      break;
    case 'string':
      if (/^\d+$/.test(v.trim())) {
        props.obj[key] = +(v.trim())
      }
  }
}

function objComp(value: number|boolean|string|null) {
  switch(typeof value) {
    case 'number': return ElInputNumber
    case 'boolean': return ElSwitch   
    default: return ElInput
  }
}

const refreshRaw = () => {
  raw = _.cloneDeep(props.obj)
}

const doneChange = () => {
  if (!changed) {
    return
  }
  emit('change', {
    objId: props.objId,
    newValue: props.obj, 
    oldValue: raw, 
    refreshRaw, 
    reset,
  })
  raw = _.cloneDeep(props.obj)
  changed = false
  switchEditable()
}

const title = computed(() => {
  if (typeof props.titleKey === 'function') {
    return props.titleKey(props.obj)
  }
  for (const key of [props.titleKey, 'title', 'name']) {
    if (key in props.obj) {
      return props.obj[key]
    }
  }
  return props.objId
})

const addKey = () => {
  if (!editing.value) return
  ElMessageBox.prompt('Please input key name', 'Tip', {
    confirmButtonText: 'OK',
    cancelButtonText: 'Cancel',
    inputPattern: /^[a-zA-Z_]\w+$/,
    inputErrorMessage: 'Invalid Key name',
  }).then(({ value }) => {
    props.obj[value] = null
  }).catch(() => {
    ElMessage({
      type: 'info',
      message: 'Canceled'
    })
  })
}

const removeKey = (key: string) => {
  ElMessageBox.confirm(`confirm remove [${key}]?`, 'Warning', {
    type: 'warning',
    confirmButtonText: 'OK',
    cancelButtonText: 'Cancel'
  }).then(() => {
    delete props.obj[key]
  })
}

defineExpose({
  refreshRaw,
  reset,
  switchEditable,
})
</script>


<template>
  <div class="ds-auto-proto-card">
    <div class="ds-apc-header">
        {{ title }}
      <template v-if="editable">
        &nbsp; | &nbsp;
        <template v-if="!editing">
        <el-icon class="mouse-hover" @click="switchEditable()"><Edit /></el-icon>
        <slot name="header" :data="obj" v-if="$slots.header"></slot>
        </template>
        <template v-else>
          <el-space :style="{height: '1.2rem'}">
            <el-icon class="mouse-hover" @click="doneChange" title="save"><SuccessFilled /></el-icon>
            <el-icon class="mouse-hover" @click="() => {
              reset()
              switchEditable()
            }" title="cancel"><CloseBold /></el-icon>
            <el-icon class="mouse-hover" @click="addKey" v-if="!lockKey"> <plus /> </el-icon>
            <slot name="editor" :data="obj" :data-id="objId"></slot>
          </el-space>
        </template>
      </template>
    </div>
    <div class="ds-apc-row" v-for="[key, value] in entries" :key="key">
    <div class="ds-apc-key">
      {{ prettyKey(key) }}
    </div>
    <div class="ds-apc-value">
      <!-- editing block -->
      <template v-if="editing && !immutables.includes(key)">
        <el-button :icon="Switch" type="primary" size="small" plain
         v-if="isNumber(value)" title="切换数据类型"
         @click="switchType(key)"></el-button>
        <template v-if="value === null">
          <strong>当前值为null，请选择值类型：</strong>
          <el-button round @click="() => obj[key] = ''">string</el-button>
          <el-button round @click="() => obj[key] = 0">number</el-button>
          <el-button round @click="() => obj[key] = false">booelan</el-button>
        </template>
        <component v-else :is="objComp(value)" v-model="obj[key]" size="small" />
        <el-button plain v-if="!lockKey"
          @click="() => removeKey(key)" :icon="Minus" type="info" size="small" />
      </template>
      <!-- pure value block -->
      <template v-else>
        <img :src="value" :alt="value" v-if="isIcon(value)" />
        <span :class="`ds-apc-vt-${typeof value}`">{{ value }}</span>
      </template>
    </div>
    </div>
    <footer class="ds-apc__footer" v-if="$slots.default">
    <slot :data="obj" :data-id="objId"></slot>
    </footer>
  </div>
</template>

<style lang="scss">
$headerHeight: 1.2rem;
$vPadding: 5px;
$keySize: v-bind(keySize);
.ds-auto-proto-card {
  border: 1px solid #333;
  border-top-left-radius: 5px;
  border-top-right-radius: 5px;
  margin-top: 5px;
  display: flex;
  flex-direction: column;
}
.ds-apc-row {
  display: grid;
  grid-gap: 10px;
}

.ds-apc-row {
  grid-template-columns: $keySize auto;
}

.ds-apc-header {
  padding: $vPadding 10px;
  background-color: #304455;
  color: #fcfaea;
  font-weight: bold;
  display: inline-flex;
  font-size: 14px;
  .el-space__item {
    height: $headerHeight;
    line-height: $headerHeight;
  }
  .el-icon {
    padding-left: $vPadding;
    font-size: $headerHeight;
  }
}
.ds-apc-key, .ds-apc-value {
  display: inline-flex;
  align-items: center;
  justify-items: stretch;
  flex-direction: row;
  flex-wrap: nowrap;
  justify-content: flex-start;
  padding: $vPadding * 1.2;
  font-size: 14px;
  .el-button {
    margin-left: 1rem;
  }
}

.ds-apc-key {
  justify-content: flex-end;
  background-color: #eee;
  font-weight: bold;
}
.ds-apc-value {
  color: #333;
  overflow: overlay;

  img {
    border: 0;
    height: 100%;
    width: auto;
    margin-right: 5px;
  }
  
  .el-button {
    margin-right: 1rem;
  }
}
.ds-apc-vt-boolean {
  color: #2c3e50;
  font-weight: bold;
}
.ds-apc-vt-number {
  font-weight: 600;
  color: #794902;
  font-family: Monospaced,serif;
}
.ds-apc-vt-string {
  font-family: "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "微软雅黑", Arial, sans-serif;
  color: #298404;
}
.ds-apc__footer {
  display: flex;
  padding: $vPadding;
  font-size: .8rem;
}
</style>

